import { Constructor } from "..";

export type AbstractConstructor<T> = NewableFunction & { prototype: T };
export class Token {
    constructor(public name?: string) { }
}

export type ServiceIdentifier<T = unknown> =
    Constructor<T> |
    AbstractConstructor<T> |
    CallableFunction |
    Token | string;

export function resolveToTypeWrapper(
    typeOrIdentifier: ((type?: never) => Constructor<unknown>) | ServiceIdentifier<unknown> | Function | string | undefined,
    target: Object,
    propertyName: string | Symbol,
    index?: number
)
    : { eagerType: ServiceIdentifier | null; lazyType: (type?: never) => ServiceIdentifier } {

    let typeWrapper!: { eagerType: ServiceIdentifier | null; lazyType: (type?: never) => ServiceIdentifier };

    /** typeOrIdentifier：string */
    if ((typeOrIdentifier && typeof typeOrIdentifier === 'string') || typeOrIdentifier instanceof Token) {
        typeWrapper = { eagerType: typeOrIdentifier, lazyType: () => typeOrIdentifier };
    }

    /** typeOrIdentifier：fuction */
    if (typeOrIdentifier && typeof typeOrIdentifier === 'function') {
        typeWrapper = { eagerType: null, lazyType: () => typeOrIdentifier };
    }

    /** 属性装饰，获取类型 */
    if (!typeOrIdentifier && propertyName) {
        const identifier = (Reflect as any).getMetadata('design:type', target, propertyName);

        typeWrapper = { eagerType: identifier, lazyType: () => identifier };
    }

    /** 参数装饰，index获取参数类型 */
    if (!typeOrIdentifier && typeof index == 'number' && Number.isInteger(index)) {
        const paramTypes: ServiceIdentifier[] = (Reflect as any).getMetadata('design:paramtypes', target, propertyName);
        const identifier = paramTypes?.[index];

        typeWrapper = { eagerType: identifier, lazyType: () => identifier };
    }

    return typeWrapper;

}

export class CannotInjectValueError extends Error {
    public name = 'CannotInjectValueError';

    get message(): string {
        return (
            `Cannot inject value into "${this.target.constructor.name}.${this.propertyName}". ` +
            `Please make sure you setup reflect-metadata properly and you don't use interfaces without service tokens as injection value.`
        );
    }

    constructor(private target: Constructor<unknown>, private propertyName: string) {
        super();
    }
}